﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;

namespace JXT.PrimaryKey.Batman.Domain.Repositories.Quotation
{
    /// <summary>
    /// 分时行情文件存储引擎
    /// </summary>
    public class TimeSharingQuoteStoreEngine
    {
        /// <summary>
        /// 默认存储路径
        /// </summary>
        private const string DEFAULT_DOCUMENT_ROOT = "E:\\data";

        public static readonly Type DataType = typeof(TimeSharingQuoteStruct);

        public static readonly int DataSize = Marshal.SizeOf(DataType);

        private static readonly string _defaultDataMappedFileName = "supernova_timesharing_data";

        public string DocumentRoot { get; private set; }

        public TimeSharingQuoteStoreEngine(string _documentRoot = DEFAULT_DOCUMENT_ROOT)
        {
            DocumentRoot = _documentRoot;
        }

        public void InitDocumentRoot()
        {
            if (!Directory.Exists(DocumentRoot))
            {
                Directory.CreateDirectory(DocumentRoot);
            }
        }

        public void CheckMarketDocumentRoot(byte market)
        {
            string path = Path.Combine(DocumentRoot, market.ToString());
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
        }

        public byte[] QuoteToByteArray(TimeSharingQuoteStruct quote)
        {
            IntPtr dataPtr = Marshal.AllocHGlobal(DataSize);
            Marshal.StructureToPtr(quote, dataPtr, false);
            byte[] buffer = new byte[DataSize];
            Marshal.Copy(dataPtr, buffer, 0, DataSize);
            Marshal.FreeHGlobal(dataPtr);
            return buffer;
        }

        public TimeSharingQuoteStruct ByteArrayToQuote(byte[] buffer)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (buffer.Length < DataSize)
                throw new ArgumentException(String.Format("buffer length fewer than {0}.", DataSize));
            IntPtr dataPtr = Marshal.AllocHGlobal(DataSize);
            Marshal.Copy(buffer, 0, dataPtr, DataSize);
            var obj = Marshal.PtrToStructure(dataPtr, DataType);
            Marshal.FreeHGlobal(dataPtr);
            return (TimeSharingQuoteStruct)obj;
        }

        public void Save(TimeSharingQuoteStruct quote)
        {
            CheckMarketDocumentRoot(quote.MarketType);
            string path = Path.Combine(DocumentRoot, quote.MarketType.ToString(), quote.Code);
            FileMode openMode = FileMode.Append;
            if (!File.Exists(path))
            {
                openMode = FileMode.CreateNew;
            }
            using (FileStream fs = new FileStream(path, openMode, FileAccess.Write, FileShare.Read))
            {
                var buffer = QuoteToByteArray(quote);
                fs.Write(buffer, 0, buffer.Length);
            }
        }

        public TimeSharingQuoteStruct Read(byte market, string code, long time, SearchType type = SearchType.More)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            if (!File.Exists(path))
            {
                return default(TimeSharingQuoteStruct);
            }
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                long fileSize = fs.Length;
                if (fileSize < DataSize)
                    return default(TimeSharingQuoteStruct);
                var position = GetPosition(fs, 0, fileSize - DataSize, time, type);
                if (position < 0)
                    return default(TimeSharingQuoteStruct);
                var buffer = new byte[DataSize];
                fs.Seek(position, SeekOrigin.Begin);
                fs.Read(buffer, 0, DataSize);
                return ByteArrayToQuote(buffer);
            }
        }

        public TimeSharingQuoteStruct ReadLast(byte market, string code)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            if (!File.Exists(path))
            {
                return default(TimeSharingQuoteStruct);
            }
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                long fileSize = fs.Length;
                if (fileSize < DataSize)
                    return default(TimeSharingQuoteStruct);
                fs.Seek(fs.Length - DataSize, SeekOrigin.Begin);
                var buffer = new byte[DataSize];
                fs.Read(buffer, 0, DataSize);
                return ByteArrayToQuote(buffer);
            }
        }

        public IEnumerable<TimeSharingQuoteStruct> Read(byte market, string code, long startTime, SearchType startType, long endTime, SearchType endType)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            IList<TimeSharingQuoteStruct> list = new List<TimeSharingQuoteStruct>();
            if (!File.Exists(path))
            {
                return list;
            }
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                long fileSize = fs.Length;
                if (fileSize < DataSize)
                    return list;
                long startPosition, endPosition;
                if (startTime < 0)
                    startPosition = 0;
                else
                    startPosition = GetPosition(fs, 0, fileSize - DataSize, startTime, startType);
                if (endTime <= 0)
                    endPosition = fileSize - DataSize;
                else
                    endPosition = GetPosition(fs, 0, fileSize - DataSize, endTime, endType);
                if (startPosition < 0 || endPosition < 0)
                    return list;
                fs.Seek(startPosition, SeekOrigin.Begin);
                var buffer = new byte[DataSize];
                while (fs.Position <= endPosition)
                {
                    fs.Read(buffer, 0, DataSize);
                    list.Add(ByteArrayToQuote(buffer));
                }
                return list;
            }
        }

        public long GetPosition(FileStream fs, long start, long end, long time, SearchType type = SearchType.More)
        {
            BinaryReader br = new BinaryReader(fs);
            if (start == end)
            {
                if (type == SearchType.Equl)
                {
                    fs.Seek(start, SeekOrigin.Begin);
                    long positionTime = br.ReadInt64();
                    if (positionTime == time)
                        return start;
                    return -1;
                }
                else
                {
                    return start;
                }
            }
            fs.Seek(start, SeekOrigin.Begin);
            long startTime = br.ReadInt64();
            if (time == startTime)
            {
                return start;
            }
            else if (time < startTime)
            {
                //if (start == 0)
                //    return 0;
                switch (type)
                {
                    case SearchType.Less:
                        return start - DataSize >= 0 ? start - DataSize : -1;
                    case SearchType.More:
                        return start;
                    case SearchType.Equl:
                        return -1;
                }
            }
            fs.Seek(end, SeekOrigin.Begin);
            long endTime = br.ReadInt64();
            if (time == endTime)
            {
                return end;
            }
            else if (time > endTime)
            {
                //if (end == fs.Length - DataSize)
                //    return end;
                switch (type)
                {
                    case SearchType.Less:
                        return end;
                    case SearchType.More:
                        return end + DataSize <= fs.Length - DataSize ? end + DataSize : -1;
                    case SearchType.Equl:
                        return -1;
                }
            }
            long newPosition = ((end - start) / (2 * DataSize)) * DataSize + start;
            fs.Seek(newPosition, SeekOrigin.Begin);
            long newPositionTime = br.ReadInt64();
            if (newPositionTime == time)
            {
                return newPosition;
            }
            else if (newPositionTime > time)
            {
                if (end - newPosition == DataSize)
                {
                    switch (type)
                    {
                        case SearchType.Less:
                            return newPosition - DataSize;
                        case SearchType.More:
                            return newPosition;
                        case SearchType.Equl:
                            newPosition -= DataSize;
                            break;
                    }
                }
                return GetPosition(fs, start, newPosition, time, type);
            }
            else
            {
                if (end - newPosition == DataSize)
                {
                    switch (type)
                    {
                        case SearchType.Less:
                            return newPosition;
                        case SearchType.More:
                            return newPosition + DataSize;
                        case SearchType.Equl:
                            newPosition += DataSize;
                            break;
                    }
                    
                }
                return GetPosition(fs, newPosition, end, time, type);
            }
        }

        public TimeSharingQuoteStruct MemoryMappedRead(byte market, string code, long time, SearchType type = SearchType.More)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            if (!File.Exists(path))
            {
                return default(TimeSharingQuoteStruct);
            }
            var fileInfo = new FileInfo(path);
            if (fileInfo.Length < DataSize)
                return default(TimeSharingQuoteStruct);
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open, _defaultDataMappedFileName, fileInfo.Length, MemoryMappedFileAccess.ReadWrite))
            {
                var mmv = mmf.CreateViewAccessor();
                var position = GetMemoryMappedPosition(mmv, 0, fileInfo.Length - DataSize, time, type);
                if (position < 0)
                    return default(TimeSharingQuoteStruct);
                TimeSharingQuoteStruct result = new TimeSharingQuoteStruct();
                mmv.Read<TimeSharingQuoteStruct>(position, out result);
                return result;
            }
        }

        public TimeSharingQuoteStruct MemoryMappedReadLast(byte market, string code)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            if (!File.Exists(path))
            {
                return default(TimeSharingQuoteStruct);
            }
            var fileInfo = new FileInfo(path);
            if (fileInfo.Length < DataSize)
                return default(TimeSharingQuoteStruct);
            using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(path, FileMode.Open, _defaultDataMappedFileName, fileInfo.Length, MemoryMappedFileAccess.ReadWrite))
            {
                var mmv = mmf.CreateViewAccessor();
                TimeSharingQuoteStruct result = new TimeSharingQuoteStruct();
                mmv.Read<TimeSharingQuoteStruct>(fileInfo.Length - DataSize, out result);
                return result;
            }
        }

        public IEnumerable<TimeSharingQuoteStruct> MemoryMappedRead(byte market, string code, long startTime, SearchType startType, long endTime, SearchType endType)
        {
            if (String.IsNullOrWhiteSpace(code))
                throw new ArgumentException("code error!");
            CheckMarketDocumentRoot(market);
            string path = Path.Combine(DocumentRoot, market.ToString(), code);
            IList<TimeSharingQuoteStruct> list = new List<TimeSharingQuoteStruct>();
            if (!File.Exists(path))
            {
                return list;
            }
            using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                if (fs.Length < DataSize)
                    return list;
                //using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(path, FileMode.OpenOrCreate, _defaultDataMappedFileName, fileInfo.Length, MemoryMappedFileAccess.ReadWrite))
                using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fs, _defaultDataMappedFileName, fs.Length, MemoryMappedFileAccess.ReadWrite, null, HandleInheritability.None, false))
                {
                    var mmv = mmf.CreateViewAccessor();
                    long startPosition, endPosition;
                    if (startTime < 0)
                        startPosition = 0;
                    else
                        startPosition = GetMemoryMappedPosition(mmv, 0, fs.Length - DataSize, startTime, startType);
                    if (endTime <= 0)
                        endPosition = fs.Length - DataSize;
                    else
                        endPosition = GetMemoryMappedPosition(mmv, 0, fs.Length - DataSize, endTime, endType);
                    if (startPosition < 0 || endPosition < 0)
                        return list;
                    long pos = startPosition;
                    while (pos <= endPosition)
                    {
                        var buffer = new byte[DataSize];
                        mmv.ReadArray<byte>(pos, buffer, 0, buffer.Length);
                        //TimeSharingQuoteStruct item = new TimeSharingQuoteStruct();
                        //mmv.Read<TimeSharingQuoteStruct>(pos, out item);
                        list.Add(ByteArrayToQuote(buffer));
                        //list.Add(item);
                        pos += DataSize;
                    }
                    return list;
                }
            }
        }

        private long GetMemoryMappedPosition(MemoryMappedViewAccessor mmv, long start, long end, long time, SearchType type = SearchType.More)
        {
            if (start == end)
            {
                if (type == SearchType.Equl)
                {
                    long positionTime = mmv.ReadInt64(start);
                    if (positionTime == time)
                        return start;
                    return -1;
                }
                else
                {
                    return start;
                }
            }
            long startTime = mmv.ReadInt64(start);
            if (time == startTime)
            {
                return start;
            }
            else if (time < startTime)
            {
                //if (start == 0)
                //    return 0;
                switch (type)
                {
                    case SearchType.Less:
                        return start - DataSize >= 0 ? start - DataSize : -1;
                    case SearchType.More:
                        return start;
                    case SearchType.Equl:
                        return -1;
                }
            }
            long endTime = mmv.ReadInt64(end);
            if (time == endTime)
            {
                return end;
            }
            else if (time > endTime)
            {
                //if (end == fs.Length - DataSize)
                //    return end;
                switch (type)
                {
                    case SearchType.Less:
                        return end;
                    case SearchType.More:
                        return end + DataSize <= mmv.Capacity - DataSize ? end + DataSize : -1;
                    case SearchType.Equl:
                        return -1;
                }
            }
            long newPosition = ((end - start) / (2 * DataSize)) * DataSize + start;
            long newPositionTime = mmv.ReadInt64(newPosition);
            if (newPositionTime == time)
            {
                return newPosition;
            }
            else if (newPositionTime > time)
            {
                if (end - newPosition == DataSize)
                {
                    switch (type)
                    {
                        case SearchType.Less:
                            return newPosition - DataSize;
                        case SearchType.More:
                            return newPosition;
                        case SearchType.Equl:
                            newPosition -= DataSize;
                            break;
                    }
                }
                return GetMemoryMappedPosition(mmv, start, newPosition, time, type);
            }
            else
            {
                if (end - newPosition == DataSize)
                {
                    switch (type)
                    {
                        case SearchType.Less:
                            return newPosition;
                        case SearchType.More:
                            return newPosition + DataSize;
                        case SearchType.Equl:
                            newPosition += DataSize;
                            break;
                    }

                }
                return GetMemoryMappedPosition(mmv, newPosition, end, time, type);
            }
        }
    }

    public enum SearchType
    {
        Equl = 0,
        Less,
        More
    }
}
